// * 词法环境

// *********************** Step 1. 变量
// 在 JS 中，每个运行的函数，代码块 {...} 以及整个脚本，都有一个被称为 词法环境(LexicalEnvironment) 的内部（隐藏）的关联对象。

// 词法环境对象由两部分组成：
// 1. 环境记录 —— 一个存储所有局部变量作为其属性（包括其他信息，例如 this 的值）的对象。
// 2. 对 外部词法环境 的引用，与外部代码关联（全局词法环境的外部词法环境为null）。

// *********************** Step 2. 函数声明
// 一个函数其实也是一个值，就像变量一样。
// 不同之处在于 函数声明 的初始化会被立即完成（不像 let /const 那样直到声明处才可用）。

// *********************** Step 3. 内部和外部的词法环境
// 在一个函数运行时，在调用刚开始时，会自动创建一个新的词法环境以存储这个调用的局部变量和参数。

// * [外部词法环境(全局)] 这里有 phrase变量 和 say 函数
let phrase = 'Hello'

function say(name) {
	// * [内部词法环境(函数调用)] 这里有 name 变量，内部词法环境有对外部词法环境的引用（所以这里面可以访问 phrase 和 say）
	console.log(`${phrase} ${name}`)
	// 当代码访问一个变量时，首先会搜索内部词法环境，然后搜索外部环境，然后搜索更外部的环境，以此类推，直到全局词法环境。
	// 如果在任何地方都找不到这个变量，严格模式下会报错（非严格模式下，为了向下兼容，会创建一个全局变量）。
}

say('World')

// *********************** Step 4. 返回函数
function makeCounter() {
	let conunt = 0 // 在变量所在的词法环境中更新变量。

	return function () {
		return conunt++
	}
}

// 每次 makeCounter 调用都会创建一个新的词法环境，以存储该 makeCounter 运行时的变量。
let conunt = makeCounter()

console.log(conunt()) // 0
console.log(conunt()) // 1
console.log(conunt()) // 2

// *********************************************************************************************************************

// * 闭包 是指一个函数可以记住其外部变量并可以访问这些变量。所以 JS 中所有函数都是闭包（只有一个例外 new Function）。
// * 简单来说，就是一个函数引用了外部变量，就形成了闭包。

// *********************************************************************************************************************

// * 垃圾收集
// * 通常，函数调用完成后，会将词法环境和其中的所有变量从内存中删除。因为现在没有任何对它们的引用了。与 JS 任何其他对象一样，词法环境仅在可达时才会保留在内存中。

// 返回的函数被保存，那么所有相应的词法环境对象也会保留在内存中。
// 当词法环境不可达时，它就会死去。也就是说，它仅在至少有一个嵌套函数引用它时才存在。
function f() {
	let val = 1

	return function () {
		console.log(val)
	}
}

let g = f() // g 的词法环境，对外部 f 的词法环境中的变量存在引用，所以 f 函数执行完毕后，不会被垃圾回收。

g() // 1
// console.log(g) // Function (anonymous)
g = null // ……现在内存被清理了

// *********************************************************************************************************************

// * 实际开发中的优化

// 正如我们所看到的，理论上当函数可达时，它外部的所有变量也都将存在。
// 但是实际中，JS 引擎会试图优化它。它们会分析变量的使用情况，如果从代码中可以明显看出有未使用的外部变量，那么还是会将其删除。

let val1 = 1

function fn() {
	let val2 = 2

	function g() {
		// 这里内部函数 g 由于没有对外部函数 fn 中的词法环境的变量存在引用，所以 fn 函数执行完毕后，val2 会被 JS 引擎给优化掉，
		// 但是最外层全局词法环境中的 val1 还是存在（它属于全局）。
	}

	return g
}

fn()
